/**
 ** This file is part of FK Klistret Extensions. FK Klistret Extensions 
 ** is free software: you can redistribute it and/or modify it under the 
 ** terms of the GNU General Public License as published by the Free 
 ** Software Foundation, either version 3 of the License, or (at your option) 
 ** any later version.

 ** FK Klistret Extensions is distributed in the hope that it will be 
 ** useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 ** General Public License for more details. You should have received a
 ** copy of the GNU General Public License along with FK Klistret Extensions. 
 ** If not, see <http://www.gnu.org/licenses/>
 */
package se.fk.klistret.console.ant.tasks;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Reference;

import com.ice.tar.TarInputStream;
import com.ice.tar.TarEntryEnumerator;
import com.ice.tar.TarEntry;

import java.io.File;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;

/**
 * 2011-07-11, The comments below are from 2005 and the code is has since
 * upgraded to the Ant 1.8 platform. The patterns and design of this project
 * should not be reused since they are not considered of any quality. The code
 * was written quickly without long term concerns.
 * <p>
 * 
 * 2005, Issues Unix tar unpackage command.
 * 
 * @author Matthew Young
 * 
 */
public class UnixTarTask extends Task {
	/**
	 * Path object representing a potential set of paths.
	 */
	private Path searchpath;
	/**
	 * Flag causing the tar operation to overwrite existing files.
	 */
	private boolean overwrite = false;
	/**
	 * Path to Unix Tar executable (default value: /usr/bin/tar)
	 */
	private String executable = "/usr/bin/tar";

	/**
	 * Series of controlls before returning a File object refering to the Tar
	 * achive. The search path must be validated (unique) and the achive file
	 * must exist ( recognized as a file).
	 * <p>
	 * 
	 * @throws BuildException
	 *             Thrown when search path is null, empty, or not unique. Also,
	 *             if the File object does not exist or is not recognized as a
	 *             file.
	 * 
	 */
	private File getTarFile() {

		if (searchpath == null) {
			throw new BuildException(
					"Tar file(s) path null (verify task parameters)",
					getLocation());
		}

		final String[] filenames = searchpath.list();

		final int count = filenames.length;

		if (count == 0) {
			throw new BuildException(
					"Tar file(s) path empty (verify file(s) exist)",
					getLocation());
		}

		if (count > 1) {
			throw new BuildException(
					"Tar file path must be unique (specify only one tar file)",
					getLocation());
		}

		File file = new File(filenames[0]);

		if (!file.exists()) {
			throw new BuildException("Tar file (" + file.getName()
					+ ") does not exist", getLocation());
		}

		if (!file.isFile()) {
			throw new BuildException("Tar file " + file.getName()
					+ " not recognized as a file ", getLocation());
		}

		return file;

	}

	/**
	 * Iterates through all Tar file entries to detemine if a previous version
	 * exists in the extract directory.
	 * <p>
	 * 
	 * @param file
	 *            <code>File</code> object representing archive
	 * 
	 * @return boolean true = pre-existing entry
	 * 
	 * @throws BuildException
	 *             Unchecked throw of an IOException while iterating through
	 *             archive entries.
	 * 
	 */
	private boolean existingEntry(File file) {

		try {
			TarInputStream tarInputStream = new TarInputStream(
					new FileInputStream(file));

			TarEntryEnumerator enumerator = new TarEntryEnumerator(
					tarInputStream);

			while (enumerator.hasMoreElements()) {
				TarEntry entry = (TarEntry) enumerator.nextElement();

				File extract = new File(file.getParent() + "/"
						+ entry.getName());

				if (extract.exists()) {
					return true;
				}
			}
		} catch (IOException ie) {
			throw new BuildException("Error iterating through tar entries. "
					+ ie.getMessage(), getLocation());
		}

		return false;
	}

	/**
	 * Unix tar command executed via the Java Runtime interface in a seperate
	 * process. Unix errors are captured and inserted into an Ant
	 * BuildException.
	 * 
	 * @param file
	 *            <code>File</code> object representing archive
	 * 
	 * @throws BuildException
	 *             Unchecked throw of either an IOException or
	 *             InterruptedException.
	 * 
	 */
	private void systemTar(File file) {

		try {
			String tar_command = executable + " xf " + file.getPath();

			Process tar = Runtime.getRuntime().exec(tar_command, null,
					file.getParentFile());

			tar.waitFor();

			if (tar.exitValue() != 0) {
				BufferedReader error_reader = new BufferedReader(
						new InputStreamReader(tar.getErrorStream()));

				String errors = new String();

				for (;;) {
					String error = error_reader.readLine();
					if (error == null) {
						break;
					} else {
						errors = errors.concat(error);
					}
				}

				throw new BuildException("Unix error during untar of file "
						+ file.getName() + ". " + errors);
			}
		} catch (IOException ie) {
			throw new BuildException(ie.getMessage(), getLocation());
		} catch (InterruptedException inte) {
			throw new BuildException(inte.getMessage(), getLocation());
		}

	}

	/**
	 * (Required by Ant Task implementation) Executes two sets of tar
	 * interfaces. The first interface is a Java Tar extention which loops
	 * through the tar entries and determines if an overwrite may occur. The
	 * second interface is a Unix tar call through Java Runtime/Process
	 * interfaces to preserve file dates and permissions.
	 * <p>
	 * 
	 */
	public void execute() throws BuildException {
		File file = getTarFile();

		log("Tar file " + file.getName() + " found...", Project.MSG_INFO);

		if (existingEntry(file) && !overwrite) {
			log("Tar haulted due to previously existing entry",
					Project.MSG_INFO);
			log("Tar file not deleted", Project.MSG_INFO);
		}

		systemTar(file);

		log("Tar operation successfull", Project.MSG_INFO);

		if (file.delete()) {
			log("Tar file deleted", Project.MSG_INFO);
		}
	}

	/**
	 * Sets path to Unix Tar executable.
	 * <p>
	 * 
	 * @param executable
	 *            <code>String</code>
	 * 
	 */
	public void setExecutable(String executable) {
		if (executable == null) {
			throw new BuildException("Executable value is null", getLocation());
		}

		this.executable = executable;
	}

	/**
	 * Sets overwrite to true or false. <b>overwrite</b> parameter is reflexed
	 * into this method.
	 * <p>
	 * 
	 * @param overwrite
	 *            <code>String</code>
	 * 
	 */
	public void setOverwrite(String overwrite) {
		this.overwrite = Project.toBoolean(overwrite);

		log("Overwrite set to " + overwrite, Project.MSG_INFO);
	}

	/**
	 * Adds a Ant fileset. <b>fileset</b> parameter is reflexed into this
	 * method.
	 * <p>
	 * 
	 * @param set
	 *            <code>FileSet</code>
	 * 
	 * @throws BuildException
	 *             Thrown if the FileSet object is null;
	 * 
	 */
	public void addFileset(FileSet set) {
		if (set == null) {
			throw new BuildException("FileSet value is null", getLocation());
		}

		getSearchpath().addFileset(set);
	}

	/**
	 * Defines a Path object based on the Ant Project loaded in memory.
	 * <p>
	 * 
	 * @return <code>Path</code>
	 * 
	 */
	private Path getSearchpath() {
		if (searchpath == null) {
			searchpath = new Path(getProject());

		}
		return searchpath;
	}

	/**
	 * Appends a Path object to the initial Path object returned by the <b><font
	 * size="-1">getSearchpath</font></b> method.
	 * <p>
	 * 
	 */
	public void setSearchpath(Path path) {
		getSearchpath().append(path);
	}

	/**
	 * Appends a search path reference to the initial Path object returned by
	 * the <b><font size="-1">getSearchpath</font></b> method.
	 * <p>
	 * 
	 */
	public void setSearchpathRef(Reference reference) {
		createSearchpath().setRefid(reference);
	}

	/**
	 * Creates a Path based on the initail Path object returned by the <b><font
	 * size="-1">getSearchpath</font></b> method.
	 * <p>
	 * 
	 * @return <code>Path</code>
	 * 
	 */
	public Path createSearchpath() {
		return getSearchpath().createPath();
	}

	/**
	 * Creates a Path Element based on the initail Path object returned by the
	 * <b><font size="-1">getSearchpath</font></b> method.
	 * <p>
	 * 
	 * @return <code>PathElement</code>
	 * 
	 */
	public Path.PathElement createSearchpathElement() throws BuildException {
		return getSearchpath().createPathElement();
	}
}
